//Reference: A Very Compact Rijndael S-box, D. Canright

module sBox
#(
  parameter SBOX_NUM = 4
)(
  //OUTPUTS
  output [8*SBOX_NUM - 1:0] sbox_out,
  //INPUTS
  input  [8*SBOX_NUM - 1:0] sbox_in,
  input enc_dec
);
  sBox_8 SBOX[SBOX_NUM - 1:0]
  (
    .sbox_out ( sbox_out ),
    .sbox_in  ( sbox_in  ),
    .enc_dec  ( enc_dec  )
  );
  
endmodule

module sBox_8
(
  //OUTPUTS
  output [7:0] sbox_out,
  //INPUTS
  input  [7:0] sbox_in,
  input enc_dec
);

wire [7:0] base_new_enc, base_new_dec, base_new;
wire [7:0] base_enc, base_dec;
wire [7:0] out_gf_inv8_1;

assign {base_new_enc, base_new_dec} = isomorphism(sbox_in);

assign base_new = ~(enc_dec ? base_new_enc : base_new_dec);

gf_inv_8 GF_INV8_1
(
  .out ( out_gf_inv8_1 ),
  .in  ( base_new      )
);

assign {base_enc, base_dec} = isomorphism_inv(out_gf_inv8_1);

assign sbox_out = ~(enc_dec ? base_enc : base_dec);

function [15:0] isomorphism;
  input [7:0] in;
  reg r1, r2, r3, r4, r5, r6, r7, r8, r9;
  reg [7:0] b, y;
  begin
    r1 = in[7]  ^ in[5];
    r2 = in[7] ~^ in[4];
    r3 = in[6]  ^ in[0];
    r4 = in[5] ~^ r3;
    r5 = in[4]  ^ r4;
    r6 = in[3]  ^ in[0];
    r7 = in[2]  ^ r1;
    r8 = in[1]  ^ r3;
    r9 = in[3]  ^ r8;
    
    b[7] = r7    ~^ r8;
    b[6] = r5;
    b[5] = in[1]  ^ r4;
    b[4] = r1    ~^ r3;
    b[3] = in[1]  ^ r2 ^ r6;
    b[2] = ~in[0];
    b[1] = r4;
    b[0] = in[2] ~^ r9;

    y[7] = r2;
    y[6] = in[4]  ^ r8;
    y[5] = in[6]  ^ in[4];
    y[4] = r9;
    y[3] = in[6] ~^ r2;
    y[2] = r7;
    y[1] = in[4]  ^ r6;
    y[0] = in[1]  ^ r5;
    
    isomorphism = {b, y};
  end
endfunction

function [15:0] isomorphism_inv;
  input [7:0] in;
  reg r1, r2, r3, r4, r5, r6, r7, r8, r9, r10;
  reg [7:0] b, y;
  begin
    r1  = in[7]  ^ in[3];
    r2  = in[6]  ^ in[4];
    r3  = in[6]  ^ in[0];
    r4  = in[5] ~^ in[3];
    r5  = in[5] ~^ r1;
    r6  = in[5] ~^ in[1];
    r7  = in[4] ~^ r6;
    r8  = in[2]  ^ r4;
    r9  = in[1]  ^ r2;
    r10 = r3     ^ r5;
    
    b[7] = r4;
    b[6] = r1;
    b[5] = r3;
    b[4] = r5;
    b[3] = r2 ^ r5;
    b[2] = r3 ^ r8;
    b[1] = r7;
    b[0] = r9;

    y[7] = in[4] ~^ in[1];
    y[6] = in[1]  ^ r10;
    y[5] = in[2]  ^ r10;
    y[4] = in[6] ~^ in[1];
    y[3] = r8     ^ r9;
    y[2] = in[7] ~^ r7;
    y[1] =  r6;
    y[0] = ~in[2];
    
    isomorphism_inv = {b, y};
  end
endfunction
endmodule

module gf_sq_2
(
  //OUTPUTS
  output [1:0] out,
  //INPUTS
  input [1:0] in
);
  assign out = {in[0], in[1]};
  
endmodule

module gf_sclw_2
(
  //OUTPUTS
  output [1:0] out,
  //INPUTS
  input [1:0] in
);
  assign out = {in[1] ^ in[0], in[1]};
  
endmodule

module gf_sclw2_2
(
  //OUTPUTS
  output [1:0] out,
  //INPUTS
  input [1:0] in
);
  assign out = {in[0], in[1] ^ in[0]};
  
endmodule

module gf_muls_2
(
  //OUTPUTS
  output [1:0] out,
  //INPUTS
  input [1:0] in1, in2,
  input in3, in4
);
  wire [1:0] nand_in1_in2;
  wire [1:0] nand_in3_in4;
  
  assign nand_in1_in2 = ~(in1 & in2);
  //assign nand_in3_in4 = {2{~(in3 & in4)}};
  assign nand_in3_in4 = {~(in3 & in4), ~(in3 & in4)};
  assign out = nand_in1_in2 ^ nand_in3_in4;
  
endmodule

module gf_muls_scl_2
(
  //OUTPUTS
  output [1:0] out,
  //INPUTS
  input [1:0] in1, in2,
  input in3, in4
);
  wire [1:0] nand_in1_in2;
  wire nand_in3_in4;
  
  assign nand_in1_in2 = ~(in1 & in2);
  assign nand_in3_in4 = ~(in3 & in4);
  assign out = {nand_in3_in4 ^ nand_in1_in2[0], ^nand_in1_in2};
  
endmodule

module gf_inv_4
(
  //OUTPUTS
  output [3:0] out,
  //INPUTS
  input [3:0] in
);  

wire [1:0] in_hg;
wire [1:0] in_lw;
wire xor_in_hg, xor_in_lw;

//wire [1:0] out_gf_mul_1;
wire [1:0] out_gf_mul_2;
wire [1:0] out_gf_mul_3;
//wire [1:0] out_gf_sq2_1;
//wire [1:0] out_gf_sclw2_1;
//wire [1:0] out_gf_sq2_2;
wire [1:0] out_gf_sq2_3;
wire [1:0] in_sq2_3;


assign in_hg = in[3:2];
assign in_lw = in[1:0];
assign xor_in_hg = ^in_hg;
assign xor_in_lw = ^in_lw;

/*
 gf_muls_2 GF_MUL_1
 (
    .out ( out_gf_mul_1 ),
    .in1 ( in_hg        ),
    .in2 ( in_lw        ),
    .in3 ( xor_in_hg    ),
    .in4 ( xor_in_lw    )
 );
 gf_sq_2 GF_SQ2_1
 (
    .out ( out_gf_sq2_1  ),
    .in  ( in_hg ^ in_lw )
 );
 gf_sclw2_2 GF_SCLW22_1
 (
    .out ( out_gf_sclw2_1),
    .in  ( out_gf_sq2_1  )
 );
 gf_sq_2 GF_SQ2_2
 (
    .out ( out_gf_sq2_2                  ),
    .in  ( out_gf_mul_1 ^ out_gf_sclw2_1 )
 );
 */  
 
 assign  in_sq2_3 = {~(in_hg[1] | in_lw[1]) ^ (~(xor_in_hg & xor_in_lw)), ~(xor_in_hg | xor_in_lw) ^ (~(in_hg[0] & in_lw[0]))};
 
  gf_sq_2 GF_SQ2_3
 (
    .out ( out_gf_sq2_3  ),
    .in  ( in_sq2_3      )
 );
 
  gf_muls_2 GF_MUL_2
 (
    .out ( out_gf_mul_2  ),
    .in1 ( out_gf_sq2_3  ),
    .in2 ( in_lw         ),
    .in3 ( ^out_gf_sq2_3 ),
    .in4 ( xor_in_lw     )
 );
 
  gf_muls_2 GF_MUL_3
 (
    .out ( out_gf_mul_3  ),
    .in1 ( out_gf_sq2_3  ), 
    .in2 ( in_hg         ),
    .in3 ( ^out_gf_sq2_3 ),
    .in4 ( xor_in_hg     )
 );
 
 assign out = {out_gf_mul_2, out_gf_mul_3};
 
endmodule

module gf_sq_scl_4
(
  //OUTPUTS
  output [3:0] out,
  //INPUTS
  input [3:0] in
);

wire [1:0] in_hg;
wire [1:0] in_lw;
wire [1:0] out_gf_sq2_1;
wire [1:0] out_gf_sq2_2;
wire [1:0] out_gf_sclw2_1;

assign in_hg = in[3:2];
assign in_lw = in[1:0];

  gf_sq_2 GF_SQ2_1
 (
    .out ( out_gf_sq2_1  ),
    .in  ( in_hg ^ in_lw )
 );
 
   gf_sq_2 GF_SQ2_2
 (
    .out ( out_gf_sq2_2  ),
    .in  ( in_lw         )
 );
 
  gf_sclw_2 GF_SCLW2_1
 (
    .out ( out_gf_sclw2_1),
    .in  ( out_gf_sq2_2  )
 );
 
 assign out = {out_gf_sq2_1, out_gf_sclw2_1};

endmodule

module gf_muls_4
(
  //OUTPUTS
  output [3:0] out,
  //INPUTS
  input [3:0] in1,
  input [3:0] in2
);

wire [1:0] in1_hg;
wire [1:0] in1_lw;
wire [1:0] in2_hg;
wire [1:0] in2_lw;
wire [1:0] xor_in1_hl;
wire [1:0] xor_in2_hl;
wire [1:0] out_gf_mul_1;
wire [1:0] out_gf_mul_2;
wire [1:0] out_gf_mul_scl_1;

assign in1_hg = in1[3:2];
assign in1_lw = in1[1:0];
assign in2_hg = in2[3:2];
assign in2_lw = in2[1:0];
assign xor_in1_hl = in1_hg ^ in1_lw;
assign xor_in2_hl = in2_hg ^ in2_lw;

  gf_muls_2 GF_MUL_1
 (
    .out ( out_gf_mul_1    ),
    .in1 ( in1_hg          ),
    .in2 ( in2_hg          ),
    .in3 ( in1[3] ^ in1[2] ),
    .in4 ( in2[3] ^ in2[2] )
 );
 
   gf_muls_2 GF_MUL_2
 (
    .out ( out_gf_mul_2    ),
    .in1 ( in1_lw          ),
    .in2 ( in2_lw          ),
    .in3 ( in1[1] ^ in1[0] ),
    .in4 ( in2[1] ^ in2[0] )
 );
 
   gf_muls_scl_2 GF_MUL_SCL2_1
 (
    .out ( out_gf_mul_scl_1),
    .in1 ( xor_in1_hl      ),
    .in2 ( xor_in2_hl      ),
    .in3 ( ^xor_in1_hl     ),
    .in4 ( ^xor_in2_hl     )
 );
 
 assign out = {out_gf_mul_1 ^ out_gf_mul_scl_1,  out_gf_mul_2 ^ out_gf_mul_scl_1};
endmodule

module gf_inv_8
(
  //OUTPUTS
  output [7:0] out,
  //INPUTS
  input [7:0] in
);

wire [3:0] in_hg;
wire [3:0] in_lw;
//wire [3:0] out_gf_mul4_1;
wire [3:0] out_gf_mul4_2;
wire [3:0] out_gf_mul4_3;
//wire [3:0] out_gf_scl4_1;
//wire [3:0] out_gf_inv4_1;
wire [3:0] out_gf_inv4_2;
wire [3:0] c;
wire c1, c2, c3;

assign in_hg = in[7:4];
assign in_lw = in[3:0];

/*
gf_muls_4 GF_MULS4_1
(
  .out ( out_gf_mul4_1 ),
  .in1 ( in_hg         ),
  .in2 ( in_lw         )
);

gf_sq_scl_4 GF_SQSCL4_1
(
  .out ( out_gf_scl4_1 ),
  .in  ( in_hg ^ in_lw )
);

gf_inv_4 GF_INV4_1
(
  .out ( out_gf_inv4_1                ),
  .in  ( out_gf_mul4_1 ^ out_gf_scl4_1)
);
*/

assign c1 = ~((in_hg[3] ^ in_hg[2]) & (in_lw[3] ^ in_lw[2]));
assign c2 = ~((in_hg[2] ^ in_hg[0]) & (in_lw[2] ^ in_lw[0]));
assign c3 = ~((^in_hg) & (^in_lw));
assign c  = {(~((in_hg[2] ^ in_hg[0]) | (in_lw[2] ^ in_lw[0])) ^ (~(in_hg[3] & in_lw[3]))) ^ c1 ^ c3, 
             (~((in_hg[3] ^ in_hg[1]) | (in_lw[3] ^ in_lw[1])) ^ (~(in_hg[2] & in_lw[2]))) ^ c1 ^ c2, 
             (~((in_hg[1] ^ in_hg[0]) | (in_lw[1] ^ in_lw[0])) ^ (~(in_hg[1] & in_lw[1]))) ^ c2 ^ c3, 
             ((~(in_hg[0] | in_lw[0])) ^ (~((in_hg[1] ^ in_hg[0]) & (in_lw[1] ^ in_lw[0])))) ^ (~((in_hg[3] ^ in_hg[1]) & (in_lw[3] ^ in_lw[1]))) ^ c2}; 

gf_inv_4 GF_INV4_2
(
  .out ( out_gf_inv4_2 ),
  .in  ( c             )
);

gf_muls_4 GF_MULS4_2
(
  .out ( out_gf_mul4_2 ),
  .in1 ( out_gf_inv4_2 ),
  .in2 ( in_lw         )
);

gf_muls_4 GF_MULS4_3
(
  .out ( out_gf_mul4_3 ),
  .in1 ( out_gf_inv4_2 ),
  .in2 ( in_hg         )
);

assign out = {out_gf_mul4_2, out_gf_mul4_3};
endmodule
